package com.baizhi.config;

import com.baizhi.entity.Admin;
import com.baizhi.entity.Menu;
import com.baizhi.service.IAdminService;
import com.baizhi.service.IMenuService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.*;
import java.util.stream.Collectors;

//这个注解包含@Configration注解，表示该类是一个配置类
@EnableWebSecurity
//开启注解式授权
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired //把业务类注入到当前的配置类中，因为这个配置类要调用数据库
    private IAdminService adminService;
    @Autowired
    private IMenuService menuService;
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(new UserDetailsService() {
            @Override
            public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//                //调用数据库把登录人的信息封装到springSecurity的User对象
//                QueryWrapper<Admin> wrapper=new QueryWrapper<>();
//                wrapper.eq("username",username);
//                Admin admin = adminService.getOne(wrapper);
//                if(admin == null){
//                    throw new UsernameNotFoundException(username + "用户不存在");
//                }
//                //在获取到用户信息之后，给该用户赋权，赋了3个权限
//                //后期该用户有哪些权限点，这个需要从数据库里面读取数据，不能固定写死
////                return new User(admin.getUsername(),new BCryptPasswordEncoder().encode(admin.getPassword()), AuthorityUtils.createAuthorityList("system:admin:select","system:admin:select2","sys:schedule:save")); }
//                List<Menu> menus = menuService.selectPermissionByUsername(username);
//                Set<String> permessionSet = menus.stream().map(Menu::getPerms).collect(Collectors.toSet());
//                permessionSet.remove(null);
//                permessionSet.remove("");//去除null数据
//return new User(admin.getUsername(),new BCryptPasswordEncoder().encode(admin.getPassword()), AuthorityUtils.createAuthorityList(permessionSet.toArray(new String[0])));
                Admin admin = adminService.selectOne(username);
                if(admin == null){
                    throw new UsernameNotFoundException(username + "用户不存在");
                }
                //根据用户名查询出来该用户拥有哪些权限。bz_menu
                List<Menu> menus = menuService.selectPermissionByUsername(username);
                //把menu对象中的perms属性给单独拿出来形成一个集合
                Set<String> permessionSet = menus.stream().map(Menu::getPerms).collect(Collectors.toSet());
                permessionSet.remove(null);//去除null数据
                permessionSet.remove("");//去集合里的空字符串
                //最后放入到第三个参数里面
                permessionSet.forEach(System.out::println);
                return new User(admin.getUsername(),new BCryptPasswordEncoder().encode(admin.getPassword()),
                        AuthorityUtils.createAuthorityList(permessionSet.toArray(new String[0])));

            }
            });
    }
    //springsecurity里面要求指定密码加密方式，否则会报异常。
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder(){
        return new BCryptPasswordEncoder();
    }
    /**
     * HttpSecurity 配置拦截规则 跳转 和 自定义登录页面
     *
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //permitAll这个方法的作用表示以后login、register对应的请求路径放行

        http.authorizeRequests().antMatchers("/**","/login","/register").permitAll()
                // 其它请求，必须要认证授权后才可以访问到
                .anyRequest().authenticated()
                //未登录时，设置的响应json内容
                .and().exceptionHandling().authenticationEntryPoint(new AuthenticationEntryPoint() {
            @Override
            public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
                response.setContentType("application/json;charset=utf-8");
                response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                PrintWriter out = response.getWriter();
                Map<String,Object> map = new HashMap<String,Object>();
                map.put("code",402);
                map.put("message","未登录");
                out.write(new ObjectMapper().writeValueAsString(map));
                out.flush();
                out.close();
            }
        })
                //springsecurity里面提供了自己的登录方法 那么该登录方法对应对的请求路径是什么？
                //以后我请求的登录的时候发的请求就是/login 设置登陆的请求路径
                .and().formLogin().loginProcessingUrl("/login")
                //登录成功时的处理
                .successHandler(new AuthenticationSuccessHandler() {
                    @Override
                    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
                        response.setContentType("application/json;charset=utf-8");
                        response.setStatus(HttpServletResponse.SC_OK);
                        PrintWriter out = response.getWriter();
                        Map<String,Object> map = new HashMap<String,Object>();
                        map.put("code",200);
                        map.put("message","登录成功");
                        map.put("authority",authentication.getAuthorities().stream().map(auth->auth.getAuthority()).toArray());
                        out.write(new ObjectMapper().writeValueAsString(map));
                        out.flush();
                        out.close();
                    }
                })
                //登录失败时的处理
                .failureHandler(new AuthenticationFailureHandler() {
                    @Override
                    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
                        response.setContentType("application/json;charset=utf-8");
                        response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
                        PrintWriter out = response.getWriter();
                        Map<String,Object> map = new HashMap<String,Object>();
                        map.put("code",400);
                        map.put("message","登录失败");
                        out.write(new ObjectMapper().writeValueAsString(map));
                        out.flush();
                        out.close();
                    }
                })
                //禁用csrf保护
                .and().csrf().disable().cors();
    }

    @Bean
    /**
     进行跨域的配置，注意方法名固定写法，不能修改
     */
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList("http://localhost:8080"));
        configuration.setAllowedMethods(Arrays.asList("GET","POST","PUT","DELETE","OPTIONS"));
        configuration.setAllowedHeaders(Arrays.asList("content-type"));
        configuration.setAllowCredentials(true);
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }



}